<template>
  <div class="color-picker" id="colorPicker" :style="getCommonStyle">
    <div class="title">颜色</div>
    <div class="color-board" ref="panel">
      <div class="saturation-white"></div>
      <div class="saturation-black"></div>
      <div class="saturation-pointer"></div>
    </div>
    <div class="slider">
      <div ref="bar" class="top-slider" @click="handleClick">
        <div ref="thumb" class="point"></div>
      </div>
      <div ref="alphaBar" class="bottom-slider" @click="handleAlphaClick">
        <div ref="alphaThumb" class="point" ></div>
      </div>
      <div class="straw"></div>
    </div>
    <div :data-color="false" class="value" @click="selectColor">
      <input class="input" :value="currentColor" @change="inputChange"/>
      <div :data-color="color" v-for="color in selectColorArr" :key="color" :style="{background: color}"></div>
    </div>
  </div>
</template>

<script>
import Color from './color'
import draggable from './draggable'
export default {
  name: 'ColorPicker',
  props: {
    value: {
      type: String,
      default: ''
    },
    top: {
      type: Number,
      default: 80
    },
    left: {
      type: Number,
      default: 0
    },
    zIndex: {
      type: Number,
      default: 999
    }
  },
  data() {
    const color = new Color({
      enableAlpha: true, // 是否支持透明度
      format: 'rgb'
    })
    return {
      color: color,
      opaqueSlider: 0,
      selectSlider: 0,
      selectColorArr: ['#FDC100', '#00FFEC', '#C7FF01', '#009EFF', '#F200FF'],
      offsetValue: 48,
      pointLeft: 0,
      pointTop: 0,
      sliderWidth: 172,
      alphaBack: null,
      panel: {
        left: 24,
        top: 71,
        width: 196,
        height: 120,
        background: 'red'
      }
    }
  },
  computed: {
    getCommonStyle() {
      return {
        '--background': this.panel.background,
        '--alphaBack': this.alphaBack,
        '--pointTop': this.pointTop + 'px',
        '--pointLeft': this.pointLeft + 'px',
        '--selectLeft': this.selectSlider + 'px',
        '--opaqueLeft': this.opaqueSlider + 'px',
        '--top': this.top + 'px',
        '--left': this.left + 'px',
        '--zIndex': this.zIndex
      }
    },
    hueValue() {
      return this.color.get('hue')
    },
    colorValue() {
      const hue = this.color.get('hue')
      const value = this.color.get('value')
      return { hue, value }
    },
    currentColor() {
      // this.color.value => rgba

      if (this.color.value) {
        const hex = this.colorTransform(this.color.value)
        return hex
        // return this.color.value
      }
      return ''
    }
  },
  watch: {
    currentColor: {
      immediate: true,
      handler(val) {
        this.$emit('update:value', val)
        this.$emit('change', val)
      }
    },
    hueValue() {
      this.update()
    },
    colorValue() {
      this.updatePanel()
      this.updateAlpha()
    },
    'color._alpha'() {
      this.updateAlpha()
    }
  },
  mounted() {
    const value = this.value
    if (value) {
      this.color.fromString(value)
    }

    const { bar, thumb, alphaBar, alphaThumb, panel } = this.$refs

    const alphaDragConfig = {
      drag: (event) => {
        this.handleAlphaDrag(event)
      },
      end: (event) => {
        this.handleAlphaDrag(event)
      }
    }

    const dragConfig = {
      drag: (event) => {
        this.handleDrag(event)
      },
      end: (event) => {
        this.handleDrag(event)
      }
    }

    draggable(panel, {
      drag: (event) => {
        this.handlePanelDrag(event)
      },
      end: (event) => {
        this.handlePanelDrag(event)
      }
    })
    draggable(bar, dragConfig)
    draggable(thumb, dragConfig)
    draggable(alphaBar, alphaDragConfig)
    draggable(alphaThumb, alphaDragConfig)

    this.update()
    this.updateAlpha()
    this.updatePanel()
  },
  methods: {
    inputChange(e) {
      console.log(e.target.value)
    },
    selectColor(e) {
      const color = e.target.dataset.color
      if (e.target.nodeName === 'DIV' && color) {
        this.color.fromString(color)
      }
    },
    handleClick(event) {
      const thumb = this.$refs.thumb
      const target = event.target
      if (target !== thumb) {
        this.handleDrag(event)
      }
    },
    handleDrag(event) {
      const rect = this.$el.getBoundingClientRect()
      const { thumb } = this.$refs

      let left = event.clientX - rect.left - this.offsetValue
      left = Math.min(left, this.sliderWidth - thumb.offsetWidth / 2)
      left = Math.max(0, left)
      const hue = Math.round(left / (this.sliderWidth - thumb.offsetWidth / 2) * 360)
      this.color.set('hue', hue)
    },
    update() {
      this.selectSlider = this.getThumbLeft()
    },
    getThumbLeft() {
      if (this.vertical) return 0
      const el = this.$el
      const hue = this.color.get('hue')

      if (!el) return 0
      const thumb = this.$refs.thumb
      return Math.round(hue * (this.sliderWidth - thumb.offsetWidth / 2) / 360)
    },

    handleAlphaClick(event) {
      const thumb = this.$refs.alphaThumb
      const target = event.target

      if (target !== thumb) {
        this.handleAlphaDrag(event)
      }
    },
    handleAlphaDrag(event) {
      const rect = this.$el.getBoundingClientRect()
      const thumb = this.$refs.alphaThumb

      let left = event.clientX - rect.left - this.offsetValue
      left = Math.max(0, left)
      left = Math.min(left, this.sliderWidth - thumb.offsetWidth / 2)

      this.color.set('alpha', Math.round(left / (this.sliderWidth - thumb.offsetWidth / 2) * 100))
    },
    updateAlpha() {
      this.opaqueSlider = this.getAlphaThumbLeft()
      this.alphaBack = this.getBackground()
    },
    getAlphaThumbLeft() {
      const el = this.$el
      const alpha = this.color.get('alpha')

      if (!el) return 0
      const thumb = this.$refs.alphaThumb
      return Math.round(alpha * (this.sliderWidth - thumb.offsetWidth / 2) / 100)
    },
    getBackground() {
      if (this.color && this.color.value) {
        const { r, g, b } = this.color.toRgb()
        return `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0) 0%, rgba(${r}, ${g}, ${b}, 1) 100%)`
      }
      return null
    },

    updatePanel() {
      const saturation = this.color.get('saturation')
      const value = this.color.get('value')
      const { width, height } = this.panel

      this.pointLeft = saturation * width / 100
      this.pointTop = (100 - value) * height / 100

      this.panel.background = 'hsl(' + this.color.get('hue') + ', 100%, 50%)'
    },
    handlePanelDrag(event) {
      const el = this.$el
      const rect = el.getBoundingClientRect()

      let left = event.clientX - rect.left - this.panel.left
      let top = event.clientY - rect.top - this.panel.top
      left = Math.max(0, left)
      left = Math.min(left, this.panel.width)

      top = Math.max(0, top)
      top = Math.min(top, this.panel.height)

      this.pointLeft = left
      this.pointTop = top

      this.color.set({
        saturation: left / this.panel.width * 100,
        value: 100 - top / this.panel.height * 100
      })
    },

    colorTransform(color, state = false) {
      if (state) {
        const len = color.length
        const colorArr = []
        for (let i = 1; i <= len; i++) {
          if (i % 2 === 0) {
            colorArr.push(color.slice(i - 2, i))
          }
        }
        const newColorArr = colorArr.map(item => {
          return parseInt(item, 16)
        })
        if (len === 6) {
          return `rgba(${newColorArr.join(',')}, 1)`
        }
        if (len === 8) {
          const opacity = 1 / 255
          newColorArr[3] = Math.round(opacity * parseInt(newColorArr[3]) * 100) / 100
          return `rgba(${newColorArr.join(',')})`
        }
        return `rgba(${newColorArr.join(',')})`
      } else {
        const colorArr = color.slice(5, color.length - 1).split(',')
        colorArr[3] = Math.round(parseFloat(colorArr[3]) * 255)
        const newColorArr = colorArr.map(item => {
          const val = parseFloat(item).toString(16)
          return parseInt(val, 16) < 16 ? '0' + val : val
        })
        return '#' + newColorArr.join('')
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.color-picker {
  width: 243px;
  height: 363px;
  background: #444444;
  box-shadow: 0 5px 12px 4px rgba(0, 0, 0, 0.09);
  position: absolute;
  left: var(--left);
  top: var(--top);
  z-index: var(--zIndex);

  .title {
    width: 100%;
    height: 55px;
    line-height: 55px;
    color: #FFFFFF;
    padding-left: 24px;
    border-bottom: 2px solid #5E5E5E;
  }

  .color-board {
    width: calc(100% - 47px);
    margin: 16px 23px 38px 24px;
    height: 120px;
    background: var(--background);
    position: relative;

    .saturation-white {
      background: linear-gradient(90deg,#fff,hsla(0,0%,100%,0));
    }

    .saturation-black {
      background: linear-gradient(0deg,#000,transparent);
    }

    .saturation-white, .saturation-black {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }

    .saturation-pointer {
      position: absolute;
      top: var(--pointTop);
      left: var(--pointLeft);
      width: 12px;
      height: 12px;
      background: #fff;
      border-radius: 50%;
      transform: translate(-6px, -6px);
      cursor: pointer;
    }
  }

  .slider {
    width: 100%;
    padding: 0 24px 0 23px;
    margin-bottom: 26px;

    .top-slider, .bottom-slider {
      width: 172px;
      height: 8px;
      border-radius: 6px;
      position: relative;
      margin-left: 24px;

      & .point {
        position: absolute;
        top: 0;
        left: 0;
        background: transparent;
        border-radius: 50%;
        border: 3px solid #fff;
        height: 8px;
        width: 8px;
        transform: translate(-3px,-3px);
        box-sizing: initial;
        cursor: pointer;
      }
    }

    .top-slider {
      background: linear-gradient(90deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red);

      & .point {
        left: var(--selectLeft);
      }
    }

    .bottom-slider {
      margin-top: 18px;
      background: var(--alphaBack),
      url("");
      background-size: contain;
      & .point {
        left: var(--opaqueLeft);
      }
    }
  }

  .value {
    padding: 0 24px 0 23px;
    width: 100%;
    height: 32px;
    display: flex;
    cursor: pointer;

    .input {
      width: 79px;
      height: 32px;
      background: #FFFFFF;
      border-radius: 2px;
      border: none;
      outline: none;
      margin-right: 2px;
      font-size: 15px;
      color: #333333;
    }

    & > div {
      width: 21px;
      height: 100%;
      margin-right: 2px;
    }
  }
}
</style>
